import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;

public class Main {
    // 打印帮助信息
    public static void showUsage()
    {
        String codeLocation = Main.class.getProtectionDomain().getCodeSource().getLocation().getFile();
        if (codeLocation.split("!").length > 1) {
            codeLocation = codeLocation.split("!")[0];
            codeLocation = codeLocation.split("/")[codeLocation.split("/").length - 1];
        }
        else
        {
            codeLocation = "****.jar";
        }
        System.out.println("Usage: java -jar " + codeLocation + " [--parameter <parameter value>]");
        System.out.println("Parameters:");
        System.out.println("  Common parameters:");
        System.out.println("    --conf                      Configuration file.");
        System.out.println("    --daemon                    Run server in background. default is false.");
        System.out.println("    --pid                       process pid file, default is none.");
        System.out.println("    --log_level                 log level, default is INFO.");
        System.out.println("    --log                       log file, default is CONSOLE.");
        System.out.println("    --mode                      Run mode, utot or ttou.");
        System.out.println("                                utot:Udp to Tcp(BroadCast) forwarder server.");
        System.out.println("                                ttou:Tcp to Udp forwarder server.");
        System.out.println("     --help                     print this message.");
        System.out.println("     --version                  print server version.");
        System.out.println("  utot parameters:");
        System.out.println("    --udp_host                  Udp multicast group address.");
        System.out.println("    --udp_port                  Udp bind port.");
        System.out.println("    --udp_multicast_if          Udp multicast local interface subnet, ex 192.168.1.0/24.");
        System.out.println("    --tcp_host_1                Forwarder target 1 tcp ip address.");
        System.out.println("    --tcp_port_1                Forwarder target 1 tcp ip port.");
        System.out.println("    --tcp_host_2                Forwarder target 2 tcp ip address.");
        System.out.println("    --tcp_port_2                Forwarder target 2 tcp ip port.");
        System.out.println("    ....                        You can set multi forwarder target (at least 1) if necessary.");
        System.out.println("    --filter_udp_port           Udp filter port, will ignore all traffic from this port.");
        System.out.println("  ttou parameters:");
        System.out.println("    --udp_host                  Udp multicast group address.");
        System.out.println("    --udp_port                  Udp bind port.");
        System.out.println("    --udp_multicast_if          Udp multicast local interface subnet, ex 192.168.1.0/24.");
        System.out.println("    --tcp_host                  Local tcp bind ip address, default is 0.0.0.0");
        System.out.println("    --tcp_port                  Local tcp bind ip port.");
        System.out.println("    --udp_local_port            Udp sender local port.");
    }

    public static void main(String[] args){
        // 获得当前程序启动的JAVA_HOME
        String javaHome = System.getProperty("java.home");

        // 获取当前JAR包所在的目录
        URL url = Main.class
                .getProtectionDomain()
                .getCodeSource()
                .getLocation();
        String jarPath;
        jarPath = URLDecoder.decode(url.getPath(), StandardCharsets.UTF_8);
        if (jarPath.contains("/!")) {
            jarPath = jarPath.substring(0, jarPath.indexOf("/!"));
        }
        jarPath = jarPath.replace("nested:", "");
        if (!jarPath.endsWith(".jar"))
        {
            jarPath = null;
        }
        else
        {
            // jarPath需要进行转换, 转为操作系统下的路径方式
            try {
                jarPath = new File(jarPath).getCanonicalPath();
            }
            catch (IOException ignored) {}
        }

        // 处理应用程序参数
        Map<String, String> appOptions = new HashMap<>();
        Logger logger = null;

        String paramName = null;
        String paramValue;
        for (String arg : args) {
            if (arg.startsWith("--")) {
                paramName = arg.substring(2);
            }
            else
            {
                if (paramName == null)
                {
                    // 最后只能有一个命令参数存在
                    System.err.println(
                            "Error: Option name missed.");
                    showUsage();
                    System.exit(255);
                }
                paramValue = arg;
                appOptions.put(paramName.toLowerCase(), paramValue);
                paramName = null;
            }
        }
        if (paramName != null)
        {
            appOptions.put(paramName.toLowerCase(), null);
        }

        // 检查是否用后台方式启动
        boolean daemonMode = appOptions.containsKey("daemon") && appOptions.get("daemon").equalsIgnoreCase("true");

        // 如果有配置文件，用配置文件中数据进行更新
        if (appOptions.containsKey("conf"))
        {
            ServerConfiguration serverConfiguration = new ServerConfiguration();
            serverConfiguration.loadConfigurationFile(appOptions.get("conf"));
            if (serverConfiguration.getDaemonMode())
            {
                daemonMode = true;
            }
        }

        if (daemonMode)
        {
            if (jarPath != null) {
                String javaCommand;
                if (System.getProperty("os.name").toLowerCase().contains("win")) {
                    javaCommand = Path.of(javaHome, "bin", "java.exe").toString();
                } else {
                    javaCommand = Path.of(javaHome, "bin", "java").toString();
                }
                List<String> daemonCommand = new ArrayList<>();
                // JAVA_BIN
                daemonCommand.add(javaCommand);
                // JAR包
                daemonCommand.add("-jar");
                daemonCommand.add(jarPath);
                // 复制其他参数
                for (String paramKey : appOptions.keySet()) {
                    if (paramKey.equalsIgnoreCase("daemon")) {
                        continue;
                    }
                    daemonCommand.add("--" + paramKey);
                    if (appOptions.get(paramKey) != null) {
                        daemonCommand.add(appOptions.get(paramKey));
                    }
                }
                // 子进程不能带有daemon参数，避免循环
                daemonCommand.add("--daemon");
                daemonCommand.add("false");

               // 非阻塞执行
                try {
                    long pid = Utils.launchDaemon(daemonCommand.toArray(new String[0]));
                    System.out.println("Daemon pid [" + pid + "] has successful started in background.");
                    System.exit(0);
                } catch (Exception se) {
                    se.printStackTrace(System.err);
                    System.exit(255);
                }
            }
            else
            {
                System.err.println("Error: Daemon is not supported in dev mode. Ignored.");
            }
        }

        // 从资源信息中读取系统的版本号
        String version, localBuildDate;
        try {
            InputStream inputStream = Main.class.getResourceAsStream("/version.properties");
            Properties properties = new Properties();
            properties.load(inputStream);
            version = properties.getProperty("version", "{project.version}");
            String buildTimestamp = properties.getProperty("build.timestamp", "${build.timestamp}");

            // 转换编译的时间格式
            try {
                ZonedDateTime zdt = ZonedDateTime.parse(buildTimestamp, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX"));
                LocalDateTime localDateTime = LocalDateTime.ofInstant(zdt.toInstant(), ZoneId.systemDefault());
                localBuildDate =
                        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").format(localDateTime) + " " +
                                TimeZone.getTimeZone(ZoneId.systemDefault()).getID();
            }
            catch (DateTimeParseException ex)
            {
                localBuildDate = buildTimestamp;
            }
            if (inputStream != null) {
                inputStream.close();
            }
        }
        catch (IOException ioe)
        {
            version = "{project.version}";
            localBuildDate = "${build.timestamp}";
        }

        try
        {
            ServerConfiguration serverConfiguration = new ServerConfiguration();
            if (appOptions.containsKey("help"))
            {
                // 打印帮助信息
                showUsage();
                System.exit(0);
            }
            if (appOptions.containsKey("version"))
            {
                // 打印版本信息
                System.out.println("[SERVER] VERSION: " + version);
                System.out.println("[SERVER] Build Time: " + localBuildDate );
                System.exit(0);
            }

            // 如果有配置文件，用配置文件中数据进行更新
            if (appOptions.containsKey("conf"))
            {
                serverConfiguration.loadConfigurationFile(appOptions.get("conf"));
            }
            if (appOptions.containsKey("log_level"))
            {
                serverConfiguration.setLog_level(appOptions.get("log_level"));
            }
            if (appOptions.containsKey("log"))
            {
                serverConfiguration.setLog(appOptions.get("log"));
            }
            if (appOptions.containsKey("pid"))
            {
                serverConfiguration.setPid(appOptions.get("pid"));
            }

            if (appOptions.containsKey("mode"))
            {
                serverConfiguration.setMode(appOptions.get("mode"));
            }
            if (appOptions.containsKey("udp_host"))
            {
                serverConfiguration.setUdp_host(appOptions.get("udp_host"));
            }
            if (appOptions.containsKey("udp_port"))
            {
                serverConfiguration.setUdp_port(appOptions.get("udp_port"));
            }
            if (appOptions.containsKey("tcp_host"))
            {
                serverConfiguration.setTcp_host(appOptions.get("tcp_host"));
            }
            if (appOptions.containsKey("tcp_port"))
            {
                serverConfiguration.setTcp_port(appOptions.get("tcp_port"));
            }
            if (appOptions.containsKey("udp_multicast_if"))
            {
                serverConfiguration.setUdp_multicast_if(appOptions.get("udp_multicast_if"));
            }
            if (appOptions.containsKey("udp_local_port"))
            {
                serverConfiguration.setUdp_local_port(appOptions.get("udp_local_port"));
            }
            if (appOptions.containsKey("filter_udp_port"))
            {
                serverConfiguration.setFilter_udp_port(appOptions.get("filter_udp_port"));
            }

            for (String key : appOptions.keySet())
            {
                if (key.toLowerCase().startsWith("tcp_host_"))
                {
                    serverConfiguration.setTcpTarget_host(key.toLowerCase().replace("tcp_host_",""), appOptions.get(key));
                }
                if (key.toLowerCase().startsWith("tcp_port_"))
                {
                    serverConfiguration.setTcpTarget_port(key.toLowerCase().replace("tcp_port_",""), appOptions.get(key));
                }
            }

            // 初始化日志服务
            logger = AppLogger.createLogger(
                    "UdpTcpForwarder",
                    serverConfiguration.getLog_level().levelStr,
                    serverConfiguration.getLog());

            // 关闭Netty的日志, 如果不是在trace下
            Logger nettyLogger = (Logger) LoggerFactory.getLogger("io.netty");
            if (!logger.isTraceEnabled()) {
                nettyLogger.setLevel(Level.OFF);
            }

            // 业务处理
            if (serverConfiguration.getMode() == null)
            {
                logger.error("[SERVER] Forwarder start fail, Missed mode.");
                showUsage();
                System.exit(1);
            }
            if (!serverConfiguration.getMode().equalsIgnoreCase("UTOT") &&
                    !serverConfiguration.getMode().equalsIgnoreCase("TTOU")
            )
            {
                logger.error("[SERVER] Forwarder start fail, unknown run mode [{}].", serverConfiguration.getMode());
                showUsage();
                System.exit(1);
            }

            // 启动服务器
            logger.info("[SERVER] Forwarder starting (PID:{})...", ProcessHandle.current().pid());
            logger.info("[SERVER] VERSION：{}", version);
            logger.info("[SERVER] Build Time: {}", localBuildDate);

            logger.info("[SERVER] Forwarder will start with [{}] mode ...", serverConfiguration.getMode().toUpperCase());
            if (serverConfiguration.getMode().equalsIgnoreCase("UTOT"))
            {
                // UDP到TCP的转发
                if (serverConfiguration.tcpTargetList.isEmpty())
                {
                    logger.warn("[SERVER] You started forwarder, but not set any tcp target. this is a dummy hole.");
                }

                UdpToTcpBroadcaster udpToTcpBroadcaster = new UdpToTcpBroadcaster(serverConfiguration, logger);
                udpToTcpBroadcaster.start();
            }
            else if  (serverConfiguration.getMode().equalsIgnoreCase("TTOU"))
            {
                TcpToUdpForwarder tcpToUdpForwarder = new TcpToUdpForwarder(serverConfiguration, logger);
                tcpToUdpForwarder.start();
            }

            // 这里永远等待，不退出
            Utils.sleep(Long.MAX_VALUE);

            // 退出应用程序
            System.exit(0);
        }
        catch (Exception se)
        {
            if (logger != null)
            {
                logger.error("[SERVER] ServerException:", se);
            }
            else
            {
                se.printStackTrace(System.err);
            }
            System.exit(255);
        }
    }
}
